﻿
package extremefx.i18n {
	import extremefx.DateTime;
	import extremefx.i18n.Calendar;	

	/**
	 * @author Marcelo Volmaro
	 * <p>This is the Hijri calendar which might be called Islamic calendar.</p>
	 * <p>The calendar supports only dates in the HijriEra starting with the epoch.</p>
	 * <p>The epoch of the Hijri Calendar might be adjusted by the addHijriDate property.</p>
	 */
	public class HijriCalendar extends Calendar {
		private static const HIJRI_ERA:uint = 1;
		private static const MIN:DateTime = new DateTime (622, 7, 18, 0, 0, 0);
		private static const MAX:DateTime = new DateTime (9999, 12, 31, 11, 59, 59);
		private static const _minFixed:int = CCHijri._fixedFromDMY(1, 1, 1);
		private static const _maxFixed:int = CCHijri._fixedFromDMY(31, 12, 9999);
		private var _addHijriDate:int;

		public function HijriCalendar() {
			super();
			_abbrEraNames = ["A.H."];
			_eraNames = ["Anno Hegirae"];
			if (_twoDigitYearMax == 99) _twoDigitYearMax = 1451;
		}
		
		/**
		 * TODO: I don't know currently, which sign to use with the parameter. In fact, I'm not sure if this
		 * is the same as <code>hijriAdjustment</code> because I converted this code from a JS implementation
		 * Value: An integer property representing the adjustment to the epoch of the Hijri calendar. 
		 * Not supported by .NET.
		 */
		public function get addHijriDate():int {
			return _addHijriDate;
		}
		
		public function set addHijriDate(pVal:int):void {
			if (pVal < -3 && pVal > 3) throw new RangeError("addHijriDate", "Value should be between -3 and 3.");
			_addHijriDate = pVal;
		}
		
		private function _checkFixedHijri(pParam:String, pRdHijri:int):void {
			if (pRdHijri < _minFixed || pRdHijri > _maxFixed - addHijriDate) {
				
				var out:Array = CCHijri._dmyFromFixed(_maxFixed - addHijriDate);
				var s:String;
				
				if (addHijriDate != 0) {
					s = XString.format("This HijriCalendar (addHijriDate {0}) allows dates from 1. 1. 1 to {1}. {2}. {3}.",
						addHijriDate, out[2], out[1], out[0]);
						
				} else {
					s = XString.format("HijriCalendar allows dates from 1.1.1 to {0}.{1}.{2}.", out[2], out[1], out[0]);
				}
				
				throw new RangeError(pParam, s);
			}
		}
		
		private function _checkDateTime(pTime:DateTime):void{
			var rd:int = CCFixed._fromDateTime(pTime) - addHijriDate;
			_checkFixedHijri("pTime", rd);
		}
		
		private function _fromDateTime(pTime:DateTime):int{
			return CCFixed._fromDateTime(pTime) - addHijriDate;
		}
		
		private function _toDateTime(pRd:int):DateTime {
			return CCFixed._toDateTime(pRd + addHijriDate);
		}
		
		private function _toDateTime2(pDate:int, pHour:int, pMinute:int, pSecond:int, pMSecond:int):DateTime {
			return CCFixed._toDateTime2(pDate + addHijriDate, pHour, pMinute, pSecond, pMSecond);
		}
		
		private function _checkEra(pEra:Array):void {
			var era:uint = pEra[0];
			if (era == -1) era = HIJRI_ERA;
			if (era != HIJRI_ERA) throw new ArgumentError("Era value was not valid.");
			pEra[0] = era;
		}
		
		protected override function _checkYE(pYear:int, pEra:Array):void {
			_checkEra(pEra);
			_argumentInRange("pYear", pYear, 1, 9666);
		}
		
		protected function _checkYME(pYear:int, pMonth:int, pEra:Array):void {
			_checkYE(pYear, pEra);

			if (pMonth < 1 || pMonth > 12) throw new RangeError("pMonth", "Month must be between one and twelve.");
			
			if (pYear == 9666) {
				_checkFixedHijri("pMonth", CCHijri._fixedFromDMY(1, pMonth, pYear));
			}
		}
		
		protected function _checkYMDE(pYear:int, pMonth:int, pDay:int, pEra:Array):void {
			_checkYME(pYear, pMonth, pEra);
			_argumentInRange("pDay", pDay, 1, getDaysInMonth(pYear, pMonth, HIJRI_ERA));
			
			if (pYear == 9666) {
				_checkFixedHijri("pDay", CCHijri._fixedFromDMY(pDay, pMonth, pYear));
			}
		}
		

		public override function addMonths(pTime:DateTime, pMonths:int):DateTime {
			var rd:int = _fromDateTime(pTime);
			var out:Array = CCHijri._dmyFromFixed(rd);
			var remainder:Array = [];
			var month:int = pMonths + int(out[1]);
			var year:int = CCMath.divMod(remainder, month, 12);
			month = int(remainder[0]);
			rd = CCHijri._fixedFromDMY(int(out[2]), month, year);
			_checkFixedHijri("pTime", rd);
			var t:DateTime = _toDateTime(rd);
			t.add(pTime.timeOfDay);
			return t;
		}
		
		public override function addYears(pTime:DateTime, pYears:int):DateTime {
			var rd:int = _fromDateTime(pTime);
			var out:Array = CCHijri._dmyFromFixed(rd);
			
			pYears += int(out[0]);
			rd = CCHijri._fixedFromDMY(int(out[2]), int(out[1]), pYears);
			_checkFixedHijri("pTime", rd);
			var t:DateTime = _toDateTime(rd);
			t.add(pTime.timeOfDay);
			return t;
		}
		
		public override function getDayOfMonth(pTime:DateTime):int {
			var rd:int = CCFixed._fromDateTime(pTime);
			_checkFixedHijri("pTime", rd);
			return CCHijri._dayFromFixed(rd);
		}
		
		public override function getDaysInMonth(pYear:int, pMonth:int, pEra:int = -1):int {
			_checkYME(pYear, pMonth, [pEra]);
			var rd1:int = CCHijri._fixedFromDMY(1, pMonth, pYear);
			var rd2:int = CCHijri._fixedFromDMY(1, pMonth+1, pYear);
			return rd2 - rd1;
		}
		
		public override function getDaysInYear(pYear:int, pEra:int = -1):int {
			_checkYE(pYear, [pEra]);
			var rd1:int = CCHijri._fixedFromDMY(1, 1, pYear);
			var rd2:int = CCHijri._fixedFromDMY(1, 1, pYear+1);
			return rd2 - rd1;
		}
		
		public override function getEra(pTime:DateTime):int {
			_checkDateTime(pTime);
			return HIJRI_ERA;
		}
		
		public override function getLeapMonth(pYear:int, pEra:int = -1):int {
			return 0;
		}
		
		public override function getMonth(pTime:DateTime):int {
			var rd:int = CCFixed._fromDateTime(pTime);
			_checkFixedHijri("pTime", rd);
			return CCHijri._monthFromFixed(rd);
		}
		
		public override function getMonthsInYear(pYear:int, pEra:int = -1):int {
			_checkYE(pYear, [pEra]);
			return 12;
		}
		
		public override function getYear(pTime:DateTime):int {
			var rd:int = CCFixed._fromDateTime(pTime);
			_checkFixedHijri("pTime", rd);
			return CCHijri._yearFromFixed(rd);
		}
		
		public override function isLeapDay(pYear:int, pMonth:int, pDay:int, pEra:int= -1):Boolean {
			_checkYMDE(pYear, pMonth, pDay, [pEra]);
			return isLeapYear(pYear) && pMonth == 12 && pDay == 30;
		}
		
		public override function isLeapMonth(pYear:int, pMonth:int, pEra:int= -1):Boolean {
			_checkYME(pYear, pMonth, [pEra]);
			return false;
		}
		
		public override function isLeapYear(pYear:int, pEra:int= -1):Boolean {
			_checkYE(pYear, [pEra]);
			return CCHijri._isLeapYear(pYear);
		}
		
		public override function toDateTime(pYear:int, pMonth:int, pDay:int, pHour:int, pMinute:int, pSecond:int, pMSecond:int, pEra:int = -1):DateTime {
			_checkYMDE(pYear, pMonth, pDay, [pEra]);
			_checkHMSM(pHour, pMinute, pSecond, pMSecond);
			var rd:int = CCHijri._fixedFromDMY(pDay, pMonth, pYear);
			return _toDateTime2(rd, pHour, pMinute, pSecond, pMSecond);
		}
		
		public override function get algorithmType():uint{
			return CalendarAlgorithmType.LUNAR;
		}
		
		public override function get minSupportedDateTime():DateTime {
			return MIN;
		}
		
		public override function get maxSupportedDateTime():DateTime {
			return MAX;
		}
		
		public function get hijriAdjustment():int {
			throw new Error("Not Implemented");
		}
		
		public function set hijriAdjustment(pVal:int):void {
			throw new Error("Not Implemented");
		}
		
		public override function get eras():Array {
			return [HIJRI_ERA];  
		}
		
		public override function set twoDigitYearMax(pYear:int):void{ 
			_argumentInRange ("value", pYear, 100, _getMaxYear());
			_twoDigitYearMax = pYear;
		}	
		
	}
}
